﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace Framework.Extension.UI.LoopList
{
    public abstract class LoopListItem : MonoBehaviour
    {
        public abstract void SetData(object data);
    }

    public class LoopListEx : MonoBehaviour, IBeginDragHandler, IEndDragHandler
    {
        public enum Direction { Vertical, Horizontal }
        public enum SlotSizeMode { Fixed, Variant }
        struct Slot
        {
            public int index;
            public RectTransform rectTransform;
        }

        public ScrollRectEx scrollRect { get; private set; }

        [SerializeField]
        private Direction direction;

        [SerializeField]
        private GameObject slotPrefab;

        [SerializeField]
        private SlotSizeMode slotSizeMode;

        [SerializeField]
        private Vector2 slotSize;

        [SerializeField]
        private Vector2 slotOffset;

        [SerializeField]
        private int countPerRow = 1;

        [SerializeField]
        private int updateCountPerFrame = 2;
        private RectTransform content;
        private bool dirty = false;
        private bool draging = false;
        private bool isRefresh = false;

        public IList dataList { get; private set; }
        public bool stateError { get; private set; }
        public Func<object, float> calculator;
        public bool invert = false;//列表反置，聊天
        private int variantModeCurrentIndex;
        private float variantModeCurrentHeight;
        private List<int> varaintAddIndexList;
        private List<float> varaintAddHList;
        private Dictionary<int, float> changeVaraintAddHList;

        private int displayRowCount;
        private float gap;
        private Stack<GameObject> slotCache = new Stack<GameObject>();
        private List<Slot> slots = new List<Slot>();
        private Vector2 originPosition;
        private Vector2 deltaPosition;
        private int displayCount;
        private bool firstFrame = true;
        private bool everCalculateSize = false;
        private bool goDown = false;

        public void MarkDirty()
        {
            if (stateError) return;
            dirty = true;
        }

        public void MarkDrag(bool flag) 
        {
            if (stateError) return;
            draging = flag;
        }

        private void Start()
        {
            scrollRect = GetComponent<ScrollRectEx>();

            if (scrollRect == null || slotPrefab == null)
            {
                UnityEngine.Debug.LogErrorFormat("{0} is null", scrollRect == null ? "scrollRect" : "slotPrefab");

                stateError = true;
                return;
            }

            content = scrollRect.content;
            scrollRect.onValueChanged.AddListener(OnScroll);
        }

        private void MarkListChanged()
        {
            MarkDirty();
            everCalculateSize = false;
        }

        public void SetData(IList dataList, bool refresh = false)
        {
            if (dataList == null) throw new System.NullReferenceException("data list is null");

            this.dataList = dataList;
            isRefresh = refresh ? refresh : false;

            if (!isRefresh)
            {
                for (int it = slots.Count - 1; it >= 0; it--)
                {
                    var slot = slots[it];
                    var slotObject = slot.rectTransform.gameObject;
                    slotObject.SetActive(false);
                    slotCache.Push(slotObject);
                }

                slots.Clear();
                tmpList.Clear();
                tmpRemoveList.Clear();
            }

            MarkListChanged();
        }

        public void SetVariant(IList dataList, Func<object, float> calculator = null)
        {
            SetData(dataList);
            this.calculator = calculator;
        }

        public void Insert(int index, object item)
        {

        }

        public void Add(object item)
        {
            if (draging) 
            {
                tmpList.Add(item);
                return;
            }

            this.dataList.Add(item);
            MarkListChanged();
        }

        public List<object> tmpList = new List<object>();
        public List<int> tmpRemoveList = new List<int>();
        public void LoadData() 
        {
            // UnityEngine.Debug.Log(tmpList.Count + "___" + tmpRemoveList.Count);

            foreach (var item in tmpList)
            {
                this.dataList.Add(item);
            }

            for (int i = 0; i < tmpRemoveList.Count; i++)
            {
                this.dataList.RemoveAt(0);
            }


            tmpList.Clear();
            tmpRemoveList.Clear();
            MarkListChanged();
        }

        public void RemoveAt(int index, int count)
        {
            if (draging)
            {
                tmpRemoveList.Add(index);
                return;        }

            this.dataList.RemoveAt(index);
            MarkListChanged();
        }

        private Vector2 scrollPos = Vector2.zero;
        private void OnScroll(Vector2 pos)
        {
            MarkDirty();
        }

        public void OnBeginDrag(PointerEventData eventData)
        {
            MarkDrag(true);
        }

        public void OnEndDrag(PointerEventData eventData)
        {
            MarkDrag(false);
            LoadData();
        }


        private void Update()
        {
            if (!dirty) return;

            if (firstFrame)
            {
                firstFrame = false;
                return;
            }

            var dataList = this.dataList;

            if (dataList == null)
            {
                dirty = false;
                foreach (var slot in slots)
                {
                    slot.rectTransform.gameObject.SetActive(false);
                    slotCache.Push(slot.rectTransform.gameObject);
                }
                slots.Clear();
                return;
            }

            if (slotSizeMode == SlotSizeMode.Variant)
            {
                VariantModeUpdate();
                return;
            }

            if (!everCalculateSize)
            {
                everCalculateSize = true;

                var contentSize = content.rect.size;
                var viewportSize = scrollRect.viewport.rect.size;

                if (direction == Direction.Vertical)
                {
                    float normallized = scrollRect.verticalNormalizedPosition;
                    gap = (contentSize.x - slotSize.x * countPerRow) / countPerRow;
                    deltaPosition = new Vector2(slotSize.x + gap, -slotSize.y - gap);

                    int rowCount = Mathf.CeilToInt(dataList.Count / (float)countPerRow);
                    contentSize.y = Mathf.Max(viewportSize.y, -deltaPosition.y * rowCount);
                    content.sizeDelta = new Vector2(0f, contentSize.y);

                    originPosition = 0.5f * gap * new Vector2(1, -1) + 0.5f * new Vector2(-contentSize.x, contentSize.y) + 0.5f * new Vector2(slotSize.x, -slotSize.y) + slotOffset;
                    displayRowCount = Mathf.CeilToInt((viewportSize.y - gap) / (slotSize.y + gap)) + 1;
                    displayCount = displayRowCount * countPerRow;

                    if ((invert && normallized <= 0.0005) || goDown){
                        goDown = false;
                        scrollRect.verticalNormalizedPosition = 0;
                    }                
                }
                else
                {
                    gap = (contentSize.y - slotSize.y * countPerRow) / countPerRow;
                    deltaPosition = new Vector2(slotSize.x + gap, -slotSize.y - gap);

                    int rowCount = Mathf.CeilToInt(dataList.Count / (float)countPerRow);
                    contentSize.x = Mathf.Max(viewportSize.x, deltaPosition.x * rowCount);
                    content.sizeDelta = new Vector2(contentSize.x, 0f);

                    originPosition = 0.5f * gap * new Vector2(1, -1);

                    displayRowCount = Mathf.CeilToInt((viewportSize.x - gap) / (slotSize.x + gap)) + 1;
                    displayCount = displayRowCount * countPerRow;
                }
            }

            var contentPosition = content.anchoredPosition;
            int updateCount = 0;

            if (direction == Direction.Vertical)
            {
                int startIndex = Mathf.FloorToInt(contentPosition.y / (gap + slotSize.y)) * countPerRow;
                int endIndex = Mathf.Min(startIndex + displayCount, dataList.Count);

                var slots = this.slots;

                if (isRefresh)
                {
                    for (int it = slots.Count - 1; it >= 0; it--)
                    {
                        var slot = slots[it];
                        SetItemData(slot.rectTransform.gameObject, dataList[slot.index]);
                    }  
                }
                else
                {
                    for (int it = slots.Count - 1; it >= 0; it--)
                    {
                        var slot = slots[it];

                        if (slot.index < startIndex || slot.index >= endIndex)
                        {
                            slot.rectTransform.gameObject.SetActive(false);
                            slotCache.Push(slot.rectTransform.gameObject);
                            slots.RemoveAt(it);
                        }
                    }

                    for (int index = Mathf.Max(0, startIndex); index < endIndex; index++)
                    {
                        for (int it = slots.Count - 1; it >= 0; it--)
                        {
                            if (slots[it].index == index) goto find;
                        }

                        int row = index / countPerRow;
                        int column = index % countPerRow;
                        var slotObject = slotCache.Count > 0 ? slotCache.Pop() : Instantiate(slotPrefab);
                        var rectTransform = slotObject.GetComponent<RectTransform>();
                        var slot = new Slot { index = index, rectTransform = rectTransform };
                        slots.Add(slot);
                        slotObject.SetActive(true);
                        rectTransform.SetParent(content, false);
                        rectTransform.anchoredPosition = originPosition + new Vector2(column * deltaPosition.x, row * deltaPosition.y);

                        // var item = slotObject.GetComponent<LoopListItem>();
                        // if (item != null) item.UpdateItem(dataList[index]);
                        SetItemData(slotObject, dataList[index]);

                        updateCount++;

                        if (updateCount > updateCountPerFrame) return;

                        find: { }
                    }
                }
            }
            else
            {

            }

            if (updateCount == 0) dirty = false;
        }

        private void VariantModeUpdate()
        {
            var dataList = this.dataList;
            int dataCount = dataList.Count;
            float normallized = scrollRect.verticalNormalizedPosition;

            if (!everCalculateSize)
            {
                everCalculateSize = true;

                if (varaintAddIndexList == null)
                {
                    varaintAddIndexList = new List<int>();
                    varaintAddHList = new List<float>();
                    changeVaraintAddHList = new Dictionary<int, float>();
                }

                var contentSize = content.rect.size;
                var viewportSize = scrollRect.viewport.rect.size;

                gap = 0.5f * (contentSize.x - slotSize.x);

                variantModeCurrentIndex = -1;
                variantModeCurrentHeight = 0.5f * gap;

                float th = 0f;
                for(int it = dataCount - 1; it >= 0; it--)
                {
                    var data = dataList[it];
                    th += CalculatorSize(data);
                }

                contentSize.y = Mathf.Max(viewportSize.y, th + dataCount * gap);
                content.sizeDelta = new Vector2(0f, contentSize.y);
                originPosition = 0.5f * gap * new Vector2(1, -1) + 0.5f * new Vector2(-contentSize.x, contentSize.y) + 0.5f * new Vector2(slotSize.x, 0f) + slotOffset;
            }

            var contentPosition = content.anchoredPosition;
            int updateCount = 0;

            if (direction == Direction.Vertical)
            {
                int startIndex;
                float startHeight;
                float h = contentPosition.y;

                float prevABS = Mathf.Abs(variantModeCurrentHeight - h);
                float zeroABS = Mathf.Abs(0.5f * gap - h);

                if (zeroABS <= prevABS)
                {
                    startIndex = -1;
                    startHeight = 0f;
                }
                else
                {
                    startIndex = variantModeCurrentIndex;
                    startHeight = variantModeCurrentHeight;
                }

                if (startHeight > h)
                {
                    while (startHeight > h && startIndex >= 0)
                    {
                        var data = dataList[startIndex];
                        float itemHeight = CalculatorSize(data);
                        startHeight -= itemHeight + gap;
                        startIndex--;
                    }
                }

                int endIndex = startIndex;
                float vh = h + scrollRect.viewport.rect.size.y;
                float th = startHeight;
                int index;

                for (index = startIndex + 1; index < dataCount; index++)
                {
                    float slotSize = CalculatorSize(dataList[index]);
                    float nextH = th + slotSize + gap;

                    if(th + slotSize <= h)
                    {
                        startIndex = index;
                        th = nextH;
                        continue;
                    }

                    changeVaraintAddHList.Add(index, -slotSize * 0.5f - th);

                    if (updateCount > updateCountPerFrame) goto find;

                    for (int it = slots.Count - 1; it >= 0; it--)
                    {
                        if (slots[it].index == index) goto find;
                    }

                    varaintAddIndexList.Add(index);
                    varaintAddHList.Add(-slotSize * 0.5f - th);

                    updateCount++;

                    find: { }

                    th = nextH;

                    if (nextH >= vh)
                    {
                        endIndex = index;
                        break;
                    }
                }
                if (index == dataCount) endIndex = dataCount - 1;

                for (int it = slots.Count - 1; it >= 0; it--)
                {
                    var slot = slots[it];

                    if (slot.index <= startIndex || slot.index > endIndex)
                    {
                        slot.rectTransform.gameObject.SetActive(false);
                        slotCache.Push(slot.rectTransform.gameObject);
                        slots.RemoveAt(it);
                    }
                }

                for(int it = varaintAddIndexList.Count - 1; it >= 0; it--)
                {
                    int sIndex = varaintAddIndexList[it];
                    float sPostionY = varaintAddHList[it];

                    var slotObject = slotCache.Count > 0 ? slotCache.Pop() : Instantiate(slotPrefab);
                    var rectTransform = slotObject.GetComponent<RectTransform>();
                    var slot = new Slot { index = sIndex, rectTransform = rectTransform };
                    slots.Add(slot);
                    slotObject.SetActive(true);
                    rectTransform.SetParent(content, false);
                    rectTransform.anchoredPosition = originPosition + new Vector2(0f, sPostionY);

                    // var item = slotObject.GetComponent<LoopListItem>();
                    // if (item != null) item.UpdateItem(dataList[sIndex]);
                    SetItemData(slotObject, dataList[sIndex]);
                }

                for (int it = slots.Count - 1; it >= 0; it--) 
                {
                    var slot = slots[it];
                    float value;

                    if (changeVaraintAddHList.TryGetValue(slot.index, out value)) 
                    {
                        float sPostionY = value;
                        slot.rectTransform.anchoredPosition = originPosition + new Vector2(0f, sPostionY);
                        SetItemData(slot.rectTransform.gameObject, dataList[slot.index]);
                    }
                }

                //dumpLog();

                varaintAddIndexList.Clear();
                varaintAddHList.Clear();
                changeVaraintAddHList.Clear();

                if ((invert && normallized <= 0.0005) || goDown){
                    goDown = false;
                    scrollRect.verticalNormalizedPosition = 0;
                }            
            }
            else
            {

            }

            if (updateCount == 0) dirty = false;
        }

        private void SetItemData(GameObject item, object data)
        {
            if (item == null)
                return;

            if (data == null)
                return;

#if ENABLE_XLUA
            LuaTarget target = item.GetComponent<LuaTarget>();
            if (target == null)
                return;

            LuaTable table = target.Table;
            if (table == null)
                return;
        
            target.InvokeLuaMethod("SetData", table, data);
#else
            LoopListItem test = item.GetComponent<LoopListItem>();
            if (test != null)
            {
                test.SetData(data);
            }
#endif
        }

        public void SetDown(bool state)
        {
            goDown = state;
        }

        private float CalculatorSize(object data)
        {
            if(calculator == null)
                return slotSize.y;
            else
                return calculator(data);
        }

        private void dumpLog()
        {
            if (varaintAddIndexList.Count <= 0)
                return;

            string log = "";
            for (int i = 0; i < varaintAddIndexList.Count; i++)
            {
                log += (varaintAddIndexList[i].ToString() + "_");
            }

            for (int i = 0; i < varaintAddHList.Count; i++)
            {
                log += (varaintAddHList[i].ToString() + "|");
            }

            UnityEngine.Debug.Log("========" + log);
            UnityEngine.Debug.Log("========" + originPosition.y);
        }
    }
}